# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Documentation on PRESUBMIT.py can be found at:
# http://www.chromium.org/developers/how-tos/depottools/presubmit-scripts

import json
import hashlib
import os
import re

import gclient_utils


def CheckChange(input_api, message_constructor):
  """Checks for files with a modified contents.

  Some checking of validator happens on builbots, but comprehensive enumeration
  tests must be run locally.

  There are two dangers:
    1. Source code for autogenerated files can be modified without regeneration
       of said files.
    2. Source of validator can be changed without running the aforementioned
       tests.

  This function catches the situation when source files for validator_x86_??.c
  are changed but files are not regenerated and it also catches the situation
  when code is changed without running the dfacheckvalidator tests.
  """

  errors = []

  changelist = input_api.change

  root_path = changelist.RepositoryRoot()

  if input_api.change.scm == 'svn':
    try:
      # With SVN you can decide to commit not all modified files but some of
      # them thus separate GetAllModifiedFiles() and GetModifiedFiles() lists
      # are provided.  We need to remove root_path from the name of file.
      assert all(filename.startswith(root_path + os.path.sep)
                 for filename in changelist.GetAllModifiedFiles())
      all_filenames = [filename[len(root_path + os.path.sep):]
                       for filename in changelist.GetAllModifiedFiles()]

      assert all(filename.startswith(root_path + os.path.sep)
                 for filename in changelist.GetModifiedFiles())
      modified_filenames = [filename[len(root_path + os.path.sep):]
                            for filename in changelist.GetModifiedFiles()]
    except:
      # If gcl is not available (which happens in CQ bots) then we'll try to use
      # AffectedFiles() instead of GetAllModifiedFiles()
      all_filenames = [file.LocalPath() for file in changelist.AffectedFiles()]
      modified_filenames = all_filenames
  else:
    # With GIT you must commit all modified files thus only AffectedFiles()
    # list is provided.
    all_filenames = [file.LocalPath() for file in changelist.AffectedFiles()]
    modified_filenames = all_filenames

  json_filename = os.path.join(
    'src', 'trusted', 'validator_ragel', 'gen', 'protected_files.json')

  protected_files = json.loads(
      gclient_utils.FileRead(os.path.join(root_path, json_filename)))

  need_dfagen = False
  need_dfacheckvalidator = False

  canonical_prefix = 'native_client/'

  for filename in sorted(all_filenames):
    canonical_filename = canonical_prefix + filename.replace('\\', '/')
    if canonical_filename in protected_files['validator']:
      file_contents = gclient_utils.FileRead(os.path.join(root_path, filename))
      sha512 = hashlib.sha512(file_contents).hexdigest()
      if sha512 != protected_files['validator'][canonical_filename]:
        errors.append(message_constructor(
          'Incorrect {0} hash:\n  expected {1}\n       got {2}'.format(
            canonical_filename,
            protected_files['validator'][canonical_filename],
            sha512)))
        need_dfacheckvalidator = True
    if canonical_filename in protected_files['generating']:
      for automaton_filename in protected_files['generated']:
        if (os.stat(os.path.join(root_path, filename)).st_mtime >
            os.stat(os.path.join(root_path,
                automaton_filename[len(canonical_prefix):])).st_mtime):
          errors.append(message_constructor(
            'File {0} is older then {1}'.format(
              automaton_filename, canonical_filename)))
          need_dfagen = True
    if (canonical_filename in protected_files['validator'] or
        canonical_filename in protected_files['generating'] or
        filename == json_filename):
      if filename not in modified_filenames:
        errors.append(message_constructor(
          'File {0} is changed but is excluded from this CL'.format(
            canonical_filename)))

  if need_dfagen:
    errors.append(message_constructor(
      'Please run "./scons dfagen" before commit!'))

  if need_dfacheckvalidator:
    errors.append(message_constructor(
      'Please run "./scons dfacheckvalidator" before commit!'))

  return errors

def CheckChangeOnUpload(input_api, output_api):
  return CheckChange(input_api,
                     message_constructor=output_api.PresubmitPromptWarning)

def CheckChangeOnCommit(input_api, output_api):
  return CheckChange(input_api,
                     message_constructor=output_api.PresubmitError)
